package errors

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"os"
)

var (
	ErrRecoverPanic  = errors.New("recover panic")
	ErrInvalid       = os.ErrInvalid    // "invalid argument"
	ErrPermission    = os.ErrPermission // "permission denied"
	ErrExist         = errors.New("already exists")
	ErrNotExist      = errors.New("not exist")
	ErrClosed        = errors.New("already closed")
	ErrNotSupport    = errors.New("not support")
	ErrUnimplemented = errors.New("not implemented")

	ErrOutOfRange = errors.New("out of range")
	ErrNotFound   = errors.New("not found")
)

func IsTimeout(err error) bool {
	if e, ok := err.(interface{ Timeout() bool }); ok && e.Timeout() {
		return true
	}
	return errors.Is(err, context.DeadlineExceeded)
}

type wrapError struct {
	Err error `json:"error"`
	Msg any   `json:"msg"`
}

func Wrap(e error, v any) error {
	return &wrapError{
		Err: e,
		Msg: v,
	}
}

func (w *wrapError) Error() string {
	if w != nil {
		return fmt.Sprintf("%v:%v", w.Err, w.Msg)
	}
	return ""
}

func (w *wrapError) Format(s fmt.State, verb rune) {
	if w == nil {
		return
	}
	switch verb {
	case 's', 'v':
		io.WriteString(s, w.Error())
	case 'q':
		fmt.Fprintf(s, "%q", w.Error())
	}
}

func (w *wrapError) Unwrap() error {
	return w.Err
}

type Errors[K comparable] map[K]error

func (x Errors[K]) Error() string {
	s, err := json.Marshal(x)
	if err == nil {
		return string(s)
	}
	str := ""
	for k, e := range x {
		str += fmt.Sprintf(",%v=%s", k, e.Error())
	}
	if len(str) > 0 {
		return str[1:]
	}
	return "no error"
}

func (x Errors[K]) Is(target error) bool {
	for _, e := range x {
		if errors.Is(e, target) {
			return true
		}
	}
	return false
}

func (x Errors[K]) As(target any) bool {
	for _, e := range x {
		if errors.As(e, target) {
			return true
		}
	}
	return false
}
